import socket, sys
from struct import *
import sqlite3 as lite
#import sql
#import plot_graphs
import time

def eth_addr (a) :
    '''takes raw mac address and convert into a colon separated
    hex string
    '''
    b = "%.2x:%.2x:%.2x:%.2x:%.2x:%.2x" % (ord(a[0]) , ord(a[1]) , ord(a[2]), ord(a[3]), ord(a[4]) , ord(a[5]))
    return b


def inspect():
    '''create a AF_PACKET type raw socket (thats basically packet level)
    define ETH_P_ALL(0x0003) i.e. Every packet. The function receives
    packets, unpacks them them into different fields as defined
    in standard packet. At each layer, it enters all fields in sql
    database and plots them in the end.
    '''
    try:
        s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, socket.ntohs(0x0003))
        size = 0
        malflag = 0
        eh_dict = dict()
        #s = socket.socket(socket.AF_PACKET, socket.SOCK_RAW, 768)

        '''socket (int domain, int type, int protocol)
        - domain is the address family specification
        - type is the socket type and
        - protocol field is used to specify the protocol
        to be used with the address family specified.
        '''

        #sql.CreateTable()
    except socket.error , msg:
        #print 'Socket could not be created. Error Code : ' + str(msg[0]) + ' Message ' + msg[1]
        print 'Socket could not be created. Error Code : {0}. Message: {1}.'.format(msg[0], msg[1])
        sys.exit()

    try:
        def tcp_packet(eth_length, iph_length, packet):
            t = iph_length + eth_length
            tcp_header = packet[t : t + 20]

            #now unpack them :)
            tcph = unpack('!HHLLBBHHH' , tcp_header)

            source_port = tcph[0]
            dest_port = tcph[1]
            sequence = tcph[2]
            acknowledgement = tcph[3]
            doff_reserved = tcph[4]
            tcph_length = doff_reserved >> 4
            flags = tcph[5]

            print 'Source Port : ' + str(source_port)
            print 'Dest Port : ' + str(dest_port)
            print 'Sequence Number : ' + str(sequence)
            print 'Acknowledgement : ' + str(acknowledgement)
            print 'TCP header length : ' + str(tcph_length)
            print 'TCP Flags : ' + str(flags)
            #print bin(int(flags))

            h_size = eth_length + iph_length + tcph_length * 4
            data_size = len(packet) - h_size

            #get data from the packet
            data = packet[h_size : ]

            #print 'Data : ' + data

            #sql.InsertDataIntoTable(eth_addr(packet[6 : 12]), eth_addr(packet[0 : 6]), str(eth_protocol), str(version), str(ihl), str(ttl), str(protocol), str(s_addr), str(d_addr), str(source_port), str(dest_port), str(sequence), str(acknowledgement), str(tcph_length), str(flags), '', '', '')

        def icmpv6_packet(eth_length, packet, size):
            u = 40 + eth_length + size
            icmph_length = 8
            icmp_header = packet[u : u + 8]

            #now unpack them :)
            icmph = unpack('!BBHHH' , icmp_header)

            icmp_type = icmph[0]
            code = icmph[1]
            checksum = icmph[2]
            identifier = icmph[3]
            sequence = icmph[4]

            print 'Type : ' + str(icmp_type),
            print 'Code : ' + str(code),
            print 'Checksum : ' + str(checksum),
            print 'Identifier : ' + str(identifier),
            print 'Sequence : ' + str(sequence)

            #sql.InsertDataIntoTable(eth_addr(packet[6 : 12]), eth_addr(packet[0 : 6]), str(eth_protocol), str(version), str(ihl), str(ttl), str(protocol), str(s_addr), str(d_addr), '', '', '', '', '', '', str(icmp_type), str(code), str(checksum))

        def fragment_header(eth_length, packet, size):
            u = eth_length + size
            fexth_length = 8
            fexth_header = packet[u : u + 8]

            #now unpack them :)
            fexth = unpack('!BBH4s' , fexth_header)

            next_header = fexth[0]
            reserved = fexth[1]
            flags = fexth[2]
            identification = fexth[3]

            print 'Next Header : ' + str(next_header),
            print 'Reserved : ' + str(reserved),
            print 'Flags : ' + str(flags),
            print 'Identification : ' + str(identification)
            return next_header

            #sql.InsertDataIntoTable(eth_addr(packet[6 : 12]), eth_addr(packet[0 : 6]), str(eth_protocol), str(version), str(ihl), str(ttl), str(protocol), str(s_addr), str(d_addr), '', '', '', '', '', '', str(icmp_type), str(code), str(checksum))

        def desOption_header(eth_length, packet, size):
            u = eth_length + size
            doh_length = 8
            doh_header = packet[u : u + 8]

            #now unpack them :)
            doh = unpack('!BB6s' , doh_header)

            next_header = doh[0]
            length = doh[1]
            options = doh[2]

            print 'Next Header : ' + str(next_header),
            print 'Length : ' + str(length),
            print 'Options : ' + str(options)
            return next_header

        def udp_packet(eth_length, packet):

            u = iph_length + eth_length
            udph_length = 8
            udp_header = packet[u : u + 8]

            #now unpack them :)
            udph = unpack('!HHHH' , udp_header)

            source_port = udph[0]
            dest_port = udph[1]
            length = udph[2]
            checksum = udph[3]

            print 'Source Port : ' + str(source_port),
            print 'Dest Port : ' + str(dest_port),
            print 'Length : ' + str(length),
            print 'Checksum : ' + str(checksum)

            h_size = eth_length + iph_length + udph_length
            data_size = len(packet) - h_size

            #get data from the packet
            data = packet[h_size:]

            #print 'Data : ' + data

            #sql.InsertDataIntoTable(eth_addr(packet[6 : 12]), eth_addr(packet[0 : 6]), str(eth_protocol), str(version), str(ihl), str(ttl), str(protocol), str(s_addr), str(d_addr), str(source_port), str(dest_port), '', '', str(length), '', '', '', str(checksum))

        def call_func (eth_length, packet, next_header, size, eh_dict):
            if next_header == 0 :
                print 'Hop-By-Hop Options Extension Header'
                size = size + 8
                if 'hbh' not in eh_dict:
                    eh_dict['hbh'] = 1
                else:
                    eh_dict['hbh'] += 1
                nh = fragment_packet(eth_length, packet, size)
                if nh in [0, 6, 17, 41, 43, 44, 58, 59, 60] and nh is not None:
                    call_func(eth_length, packet, nh, size, eh_dict)
            #TCP Packets
            elif next_header == 6 :
                print 'TCP packet'
                #tcp_packet(eth_length, iph_length, packet)
            #UDP Packets
            elif next_header == 17 :
                print 'UDP packet'
                #udp_packet(eth_length, iph_length, packet)
            elif next_header == 41 :
                print 'Encapsulated IPv6 Header'
                size = size + 8
                if 'eh' not in eh_dict:
                    eh_dict['eh'] = 1
                else:
                    eh_dict['eh'] += 1
                nh = fragment_header(eth_length, packet, size)
                if nh in [0, 6, 17, 41, 43, 44, 58, 59, 60] and nh is not None:
                    call_func(eth_length, packet, nh, size, eh_dict)
            elif next_header == 43 :
                print 'Routing Extension Header'
                size = size + 8
                if 'rh' not in eh_dict:
                    eh_dict['rh'] = 1
                else:
                    eh_dict['rh'] += 1
                nh = fragment_packet(eth_length, packet, size)
                if nh in [0, 6, 17, 41, 43, 44, 58, 59, 60] and nh is not None:
                    call_func(eth_length, packet, nh, size, eh_dict)
            elif next_header == 44 :
                print 'Fragmentation Extension Header\t'
                size = size + 8
                if 'fh' not in eh_dict:
                    eh_dict['fh'] = 1
                else:
                    eh_dict['fh'] += 1
                nh = fragment_header(eth_length, packet, size)
                if nh in [0, 6, 17, 41, 43, 44, 58, 59, 60] and nh is not None:
                    call_func(eth_length, packet, nh, size, eh_dict)
            #ICMP Packets
            elif next_header == 58 :
                print 'ICMPv6 packet\t'
                icmpv6_packet(eth_length, packet, size)
            elif next_header == 59 :
                print 'No next header'
            elif next_header == 60 :
                print 'Destination Options Extension Header'
                size = size + 8
                if 'doh' not in eh_dict:
                    eh_dict['doh'] = 1
                else:
                    eh_dict['doh'] += 1
                nh = desOption_header(eth_length, packet, size)
                if nh in [0, 6, 17, 41, 43, 44, 58, 59, 60] and nh is not None:
                    call_func(eth_length, packet, nh, size, eh_dict)
            #some other IP packet like IGMP
            else:
                print 'Protocol other than TCP/UDP/ICMP'

        def ipv6_header(eth_length, packet, size):
            ip_header = packet[eth_length + size: 40 + eth_length + size]
            size = size + 40

            #now unpack them :)
            iph = unpack('!HHHBB16s16s' , ip_header)

            version_class = iph[0]
            version = version_class >> 12
            tclass = version_class & 0xFFF
            flow_label = iph[1]
            p_length = iph[2]
            next_header = iph[3]
            ttl = iph[4]
            s_addr = socket.inet_ntop(socket.AF_INET6, iph[5]);
            d_addr = socket.inet_ntop(socket.AF_INET6, iph[6]);

            print 'IP Version : ' + str(version)
            print 'Traffic Class : ' + str(tclass)
            print 'Flow Label : ' + str(flow_label)
            print 'Payload Length : ' + str(p_length)
            print 'TTL : ' + str(ttl)
            print 'L4Protocol : ' + str(next_header)
            print 'Source Address : ' + str(s_addr)
            print 'Destination Address : ' + str(d_addr)
            if next_header in [0, 6, 17, 41, 43, 44, 58, 59, 60] and next_header is not None:
                call_func(eth_length, packet, next_header, size, eh_dict)

        # receive a packet
        while True:
            eh_dict = dict()
            packet = s.recvfrom(65565)

            #packet string from tuple
            packet = packet[0]

            #parse ethernet header
            eth_length = 14

            eth_header = packet[:eth_length]
            eth = unpack('!6s6sH' , eth_header)
            eth_protocol = socket.ntohs(eth[2])

            #print eth[2]
            #time.sleep(5)

            #Parse IP packets, IP Protocol number = 8
            if eth_protocol == 56710 :
                print '\nDestination MAC : ' + eth_addr(packet[0 : 6])
                print 'Source MAC : ' + eth_addr(packet[6 : 12])
                print 'L3Protocol : ' + str(eth_protocol)
                #Parse IPv6 header
                ipv6_header(eth_length, packet, size)
                #take first 40 characters for the ip header

                if 'fh'in eh_dict:
                    if eh_dict['fh'] > 1:
                        malflag = 1
                if 'rh' in eh_dict:
                    if eh_dict['rh'] > 1:
                        malflag = 1
                if 'doh' in eh_dict:
                    if eh_dict['doh'] > 2:
                        malflag = 1
                if 'hbh' in eh_dict:
                    if eh_dict['hbh'] > 1:
                        malflag = 1

                if malflag == 1:
                    print '!!!ALERT!!! Malformed packet detected.\n'


    except KeyboardInterrupt:
        print 'Keyboard Interrupt detected!'
        #data = sql.GetDataFromTable()
        #plot_graphs.take_usr_input(data)


def main():
    inspect()

if __name__ == '__main__':
    main()
